/**
 * @description shallow copy object | 浅拷贝对象
 * @param {*} obj
 * @returns new object
 */
export function shallowClone(obj) {
  if (typeof obj !== "object" || obj === null) {
    return obj;
  }
  return Array.isArray(obj) ? [...obj] : { ...obj };
}

/**
 * @description deep copy object | 深拷贝对象
 * @param {*} obj
 * @param {*} hash
 * @returns new object
 */
export function deepClone(obj, hash = new WeakMap()) {
  if (typeof obj !== "object" || obj === null) {
    return obj;
  }

  // 检查特殊对象类型并进行克隆
  // check special object types and clone them
  const Constructor = obj.constructor;
  switch (Constructor) {
    case RegExp:
      return new Constructor(obj);
    case Date:
      return new Constructor(obj.getTime());
    case Set:
      return new Set([...obj].map((item) => deepClone(item, hash)));
    case Map:
      return new Map([...obj].map(([k, v]) => [k, deepClone(v, hash)]));
  }

  // 如果对象是循环引用中的某个对象，则直接返回
  // if Object is already in the hash table, return it directly
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  let copy;
  if (Array.isArray(obj)) {
    copy = [];
  } else {
    copy = {};
  }

  // 将当前对象和它的拷贝存入哈希表中
  // store the current object and its copy in the hash table
  hash.set(obj, copy);

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 递归调用
      // recursively call
      copy[key] = deepClone(obj[key], hash);
    }
  }

  return copy;
}

/**
 * @description filter object by predicate function | 根据给定的判断函数过滤对象的键值对。
 * @param {Object} obj - need to filter object | 需要过滤的对象。
 * @param {function(any, string):boolean} predicate - predicate function, accept value and key as arguments, return a boolean value. | 判断函数，该函数接受对象的值和键作为参数，返回一个布尔值。
 * @returns {Object} - new object, only contains key-value pairs that pass the predicate function. | 过滤后的新对象，只包含符合判断函数的键值对。
 * @example
 * const obj = { age: 18, score: 90, name: "Tom" };
 * const filtered = filterObject(obj, (value, key) => key.length === 3);
 * console.log(filtered); // { age: 18, score: 90 }
 */
export function filterObject(obj, predicate) {
  return Object.keys(obj).reduce((acc, key) => {
    const value = obj[key];
    // 确保满足predicate条件且value不是数组
    if (predicate(value, key) && !Array.isArray(value)) {
      acc[key] = value;
    }
    return acc;
  }, {});
}

/**
 * @description map object by callback function | 根据提供的回调函数映射对象的每个键值对，并返回一个新对象。
 * @param {Object} obj - need to map object | 需要映射的原始对象。
 * @param {function(any, string):any} callback - callback function, accept value and key as arguments, return a new value. | 回调函数，该函数接受对象的值和键作为参数，返回映射后的值。
 * @returns {Object} - new object, contains the result of callback function for each key-value pair. | 映射后的新对象。
 * @example
 * const obj = { a: 1, b: 2, c: 3 };
 * const mapped = mapObject(obj, (value) => value * 2);
 * console.log(mapped); // { a: 2, b: 4, c: 6 }
 */
export function mapObject(obj, callback) {
  return Object.keys(obj).reduce((acc, key) => {
    acc[key] = callback(obj[key], key);
    return acc;
  }, {});
}

/**
 * @description transform object keys by callback function | 根据提供的回调函数转换对象的键名，并返回一个新对象。
 * @param {Object} obj - need to transform object keys | 需要键名转换的原始对象。
 * @param {function(string): string} transform - transform function, accept key as argument, return a new key. | 转换函数，该函数接受对象的键作为参数，返回转换后的键名。
 * @returns {Object} - new object, contains the same value as the original object, but with transformed keys. | 键名转换后的新对象。
 * @example
 * const obj = { firstName: "John", lastName: "Doe" };
 * const newObj = transformObjectKeys(obj, key => key.toUpperCase());
 * console.log(newObj); // { FIRSTNAME: "John", LASTNAME: "Doe" }
 */
export function transformObjectKeys(obj, transform) {
  return Object.keys(obj).reduce((acc, key) => {
    const transformedKey = transform(key);
    if (typeof transformedKey !== "string") {
      throw new TypeError("Transform function must return a string");
    }
    acc[transformedKey] = obj[key];
    return acc;
  }, {});
}

/**
 * Recursively freeze an object and all its properties. | 递归冻结一个对象及其所有属性。
 * The freezing is done in-place, and the original object is returned. | 冻结是直接在原对象上进行的，原对象会被返回。
 *
 * @param {Object} object - The object to freeze. This includes all properties that are objects or functions and are not already frozen. | 需要冻结的对象，包括所有属性，这些属性是对象或函数，且尚未被冻结。
 * @returns {Object} The same object after it has been deeply frozen. | 被深度冻结后的同一个对象。
 */
export function deepFreeze(object) {
  if (object === null || object === undefined) {
    return object;
  }

  Object.freeze(object);
  Object.getOwnPropertyNames(object).forEach(function (prop) {
    if (
      object.hasOwnProperty(prop) &&
      object[prop] !== null &&
      (typeof object[prop] === "object" ||
        typeof object[prop] === "function") &&
      !Object.isFrozen(object[prop])
    ) {
      deepFreeze(object[prop]);
    }
  });
  return object;
}

/**
 * Recursively flattens a nested object into a single level object with dot-separated keys. | 递归将一个嵌套对象展平为单层对象，并用点号分隔的键名。
 * Arrays are not treated as objects and are not flattened. | 数组不作为对象处理，不展平。
 *
 * @param {Object} obj - The object to flatten. This should be a plain object without circular references.
 * @param {string} [prefix=''] - The initial prefix to use for the keys in the flattened object.
 * This is mainly used for recursive calls and typically not set by the caller. | 初始的前缀，主要用于递归调用，通常由调用者设置。
 * @returns {Object} A new object where all nested properties are flattened to a single level, with keys representing their original path, separated by dots. | 一个新的对象，其中所有嵌套属性都展平为单层，键名表示其原始路径，用点号分隔。
 * @example
 * // returns { 'a.b.c': 1, 'a.d': 2, 'e': 3 }
 * flattenObject({ a: { b: { c: 1 }, d: 2 }, e: 3 });
 */
export function flattenObject(obj, prefix = "") {
  // 检查输入是否为对象
  if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
    return {};
  }

  return Object.keys(obj).reduce((acc, k) => {
    const pre = prefix.length ? prefix + "." : "";
    if (typeof obj[k] === "object" && obj[k] && !Array.isArray(obj[k])) {
      Object.assign(acc, flattenObject(obj[k], pre + k));
    } else {
      acc[pre + k] = obj[k];
    }
    return acc;
  }, {});
}

export default {
  shallowClone,
  deepClone,
  filterObject,
  mapObject,
  transformObjectKeys,
  deepFreeze,
  flattenObject,
};
